/*
 * AbstractPhotoController.java 5 nov 2012
 *
 * Sweet Home 3D, Copyright (c) 2012 Emmanuel PUYBARET / eTeks <info@eteks.com>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */
package com.eteks.sweethome3d.viewcontroller;

import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeSupport;
import java.lang.ref.WeakReference;

import com.eteks.sweethome3d.model.AspectRatio;
import com.eteks.sweethome3d.model.Home;
import com.eteks.sweethome3d.model.HomeEnvironment;
import com.eteks.sweethome3d.model.UserPreferences;

/**
 * The base class for controllers of photo creation views.
 * @author Emmanuel Puybaret
 * @since 4.0
 */
public abstract class AbstractPhotoController implements Controller
{
	/**
	 * The properties that may be edited by the view associated to this controller. 
	 */
	public enum Property
	{
		ASPECT_RATIO, WIDTH, HEIGHT, QUALITY, VIEW_3D_ASPECT_RATIO, CEILING_LIGHT_COLOR
	}
	
	private final Home home;
	private final View view3D;
	private final ContentManager contentManager;
	private final PropertyChangeSupport propertyChangeSupport;
	
	private AspectRatio aspectRatio;
	private int width;
	private int height;
	private int quality;
	private float view3DAspectRatio;
	private int ceilingLightColor;
	
	public AbstractPhotoController(Home home, UserPreferences preferences, View view3D, ContentManager contentManager)
	{
		this.home = home;
		this.view3D = view3D;
		this.contentManager = contentManager;
		this.propertyChangeSupport = new PropertyChangeSupport(this);
		this.view3DAspectRatio = 1;
		
		EnvironmentChangeListener listener = new EnvironmentChangeListener(this);
		home.getEnvironment().addPropertyChangeListener(HomeEnvironment.Property.PHOTO_WIDTH, listener);
		home.getEnvironment().addPropertyChangeListener(HomeEnvironment.Property.PHOTO_HEIGHT, listener);
		home.getEnvironment().addPropertyChangeListener(HomeEnvironment.Property.PHOTO_ASPECT_RATIO, listener);
		home.getEnvironment().addPropertyChangeListener(HomeEnvironment.Property.PHOTO_QUALITY, listener);
		home.getEnvironment().addPropertyChangeListener(HomeEnvironment.Property.CEILING_LIGHT_COLOR, listener);
		updateProperties();
	}
	
	/**
	 * Home environment listener that updates ceiling light color. This listener is bound to this controller 
	 * with a weak reference to avoid strong link between home and this controller.  
	 */
	private static class EnvironmentChangeListener implements PropertyChangeListener
	{
		private WeakReference<AbstractPhotoController> photoController;
		
		public EnvironmentChangeListener(AbstractPhotoController photoController)
		{
			this.photoController = new WeakReference<AbstractPhotoController>(photoController);
		}
		
		public void propertyChange(PropertyChangeEvent ev)
		{
			// If controller was garbage collected, remove this listener from home
			final AbstractPhotoController controller = this.photoController.get();
			if (controller == null)
			{
				((HomeEnvironment) ev.getSource()).removePropertyChangeListener(HomeEnvironment.Property.PHOTO_WIDTH,
						this);
				((HomeEnvironment) ev.getSource()).removePropertyChangeListener(HomeEnvironment.Property.PHOTO_HEIGHT,
						this);
				((HomeEnvironment) ev.getSource())
						.removePropertyChangeListener(HomeEnvironment.Property.PHOTO_ASPECT_RATIO, this);
				((HomeEnvironment) ev.getSource()).removePropertyChangeListener(HomeEnvironment.Property.PHOTO_QUALITY,
						this);
				((HomeEnvironment) ev.getSource())
						.removePropertyChangeListener(HomeEnvironment.Property.CEILING_LIGHT_COLOR, this);
			}
			else if (HomeEnvironment.Property.PHOTO_WIDTH.name().equals(ev.getPropertyName()))
			{
				controller.setWidth((Integer) ev.getNewValue(), false);
			}
			else if (HomeEnvironment.Property.PHOTO_HEIGHT.name().equals(ev.getPropertyName()))
			{
				controller.setHeight((Integer) ev.getNewValue(), false);
			}
			else if (HomeEnvironment.Property.PHOTO_ASPECT_RATIO.name().equals(ev.getPropertyName()))
			{
				controller.setAspectRatio((AspectRatio) ev.getNewValue());
			}
			else if (HomeEnvironment.Property.PHOTO_QUALITY.name().equals(ev.getPropertyName()))
			{
				controller.setQuality((Integer) ev.getNewValue());
			}
			else if (HomeEnvironment.Property.CEILING_LIGHT_COLOR.name().equals(ev.getPropertyName()))
			{
				controller.setCeilingLightColor((Integer) ev.getNewValue());
			}
		}
	}
	
	/**
	 * Returns the content manager of this controller.
	 */
	public ContentManager getContentManager()
	{
		return this.contentManager;
	}
	
	/**
	 * Adds the property change <code>listener</code> in parameter to this controller.
	 */
	public void addPropertyChangeListener(Property property, PropertyChangeListener listener)
	{
		this.propertyChangeSupport.addPropertyChangeListener(property.name(), listener);
	}
	
	/**
	 * Removes the property change <code>listener</code> in parameter from this controller.
	 */
	public void removePropertyChangeListener(Property property, PropertyChangeListener listener)
	{
		this.propertyChangeSupport.removePropertyChangeListener(property.name(), listener);
	}
	
	/**
	 * Updates edited properties from the photo creation preferences.
	 */
	protected void updateProperties()
	{
		HomeEnvironment homeEnvironment = this.home.getEnvironment();
		setAspectRatio(homeEnvironment.getPhotoAspectRatio());
		setWidth(homeEnvironment.getPhotoWidth(), false);
		setHeight(homeEnvironment.getPhotoHeight(), false);
		setQuality(homeEnvironment.getPhotoQuality());
		setCeilingLightColor(homeEnvironment.getCeillingLightColor());
	}
	
	/**
	 * Sets the aspect ratio of the photo.
	 */
	public void setAspectRatio(AspectRatio aspectRatio)
	{
		if (this.aspectRatio != aspectRatio)
		{
			AspectRatio oldAspectRatio = this.aspectRatio;
			this.aspectRatio = aspectRatio;
			this.propertyChangeSupport.firePropertyChange(Property.ASPECT_RATIO.name(), oldAspectRatio, aspectRatio);
			this.home.getEnvironment().setPhotoAspectRatio(this.aspectRatio);
			if (this.aspectRatio == AspectRatio.VIEW_3D_RATIO)
			{
				if (this.view3DAspectRatio != Float.POSITIVE_INFINITY)
				{
					setHeight(Math.round(width / this.view3DAspectRatio), false);
				}
			}
			else if (this.aspectRatio.getValue() != null)
			{
				setHeight(Math.round(width / this.aspectRatio.getValue()), false);
			}
		}
	}
	
	/**
	 * Returns the aspect ratio of the photo.
	 */
	public AspectRatio getAspectRatio()
	{
		return this.aspectRatio;
	}
	
	/**
	 * Sets the width of the photo.
	 */
	public void setWidth(int width)
	{
		setWidth(width, true);
	}
	
	private void setWidth(int width, boolean updateHeight)
	{
		if (this.width != width)
		{
			int oldWidth = this.width;
			this.width = width;
			this.propertyChangeSupport.firePropertyChange(Property.WIDTH.name(), oldWidth, width);
			if (updateHeight)
			{
				if (this.aspectRatio == AspectRatio.VIEW_3D_RATIO)
				{
					if (this.view3DAspectRatio != Float.POSITIVE_INFINITY)
					{
						setHeight(Math.round(width / this.view3DAspectRatio), false);
					}
				}
				else if (this.aspectRatio.getValue() != null)
				{
					setHeight(Math.round(width / this.aspectRatio.getValue()), false);
				}
			}
			this.home.getEnvironment().setPhotoWidth(this.width);
		}
	}
	
	/**
	 * Returns the width of the photo.
	 */
	public int getWidth()
	{
		return this.width;
	}
	
	/**
	 * Sets the height of the photo.
	 */
	public void setHeight(int height)
	{
		setHeight(height, true);
	}
	
	private void setHeight(int height, boolean updateWidth)
	{
		if (this.height != height)
		{
			int oldHeight = this.height;
			this.height = height;
			this.propertyChangeSupport.firePropertyChange(Property.HEIGHT.name(), oldHeight, height);
			if (updateWidth)
			{
				if (this.aspectRatio == AspectRatio.VIEW_3D_RATIO)
				{
					if (this.view3DAspectRatio != Float.POSITIVE_INFINITY)
					{
						setWidth(Math.round(height * this.view3DAspectRatio), false);
					}
				}
				else if (this.aspectRatio.getValue() != null)
				{
					setWidth(Math.round(height * this.aspectRatio.getValue()), false);
				}
			}
			this.home.getEnvironment().setPhotoHeight(this.height);
		}
	}
	
	/**
	 * Returns the height of the photo.
	 */
	public int getHeight()
	{
		return this.height;
	}
	
	/**
	 * Sets the rendering quality of the photo.
	 */
	public void setQuality(int quality)
	{
		if (this.quality != quality)
		{
			int oldQuality = this.quality;
			this.quality = Math.min(quality, getQualityLevelCount() - 1);
			this.propertyChangeSupport.firePropertyChange(Property.QUALITY.name(), oldQuality, quality);
			this.home.getEnvironment().setPhotoQuality(this.quality);
		}
	}
	
	/**
	 * Returns the rendering quality of the photo.
	 */
	public int getQuality()
	{
		return this.quality;
	}
	
	/**
	 * Returns the maximum value for quality.
	 */
	public int getQualityLevelCount()
	{
		return 4;
	}
	
	/**
	 * Sets the edited ceiling light color.
	 */
	public void setCeilingLightColor(int ceilingLightColor)
	{
		if (this.ceilingLightColor != ceilingLightColor)
		{
			int oldCeilingLightColor = this.ceilingLightColor;
			this.ceilingLightColor = ceilingLightColor;
			this.propertyChangeSupport.firePropertyChange(Property.CEILING_LIGHT_COLOR.name(), oldCeilingLightColor,
					ceilingLightColor);
			this.home.getEnvironment().setCeillingLightColor(ceilingLightColor);
		}
	}
	
	/**
	 * Returns the edited ceiling light color.
	 */
	public int getCeilingLightColor()
	{
		return this.ceilingLightColor;
	}
	
	/**
	 * Sets the aspect ratio of the 3D view.
	 */
	public void set3DViewAspectRatio(float view3DAspectRatio)
	{
		if (this.view3DAspectRatio != view3DAspectRatio)
		{
			float oldAspectRatio = this.view3DAspectRatio;
			this.view3DAspectRatio = view3DAspectRatio;
			this.propertyChangeSupport.firePropertyChange(Property.ASPECT_RATIO.name(), oldAspectRatio,
					view3DAspectRatio);
			if (this.aspectRatio == AspectRatio.VIEW_3D_RATIO && this.view3DAspectRatio != Float.POSITIVE_INFINITY)
			{
				setHeight(Math.round(this.width / this.view3DAspectRatio), false);
			}
		}
	}
	
	/**
	 * Returns the aspect ratio of the 3D view.
	 */
	public float get3DViewAspectRatio()
	{
		return this.view3DAspectRatio;
	}
	
	/**
	 * Returns the 3D view used to compute aspect ratio bound to it.
	 */
	public View get3DView()
	{
		return this.view3D;
	}
	
	/**
	 * Controls the change of value of a visual property in home.
	 * @deprecated {@link #setVisualProperty(String, Object) setVisualProperty} should be replaced by a call to 
	 * {@link #setHomeProperty(String, String)} to ensure the property can be easily saved and read.
	 */
	public void setVisualProperty(String propertyName, Object propertyValue)
	{
		this.home.setVisualProperty(propertyName, propertyValue);
	}
	
	/**
	 * Controls the change of value of a property in home.
	 * @since 5.2
	 */
	public void setHomeProperty(String propertyName, String propertyValue)
	{
		this.home.setProperty(propertyName, propertyValue);
	}
}
